/*
* GeoVISTA Center (Penn State, Dept. of Geography)
* Copyright (c), 2003-2011,  Jin Chen and Alan M. MacEachren, GeoVISTA Center, Penn State University
* All Rights Researved.
*
*
* @Original Author: jin Chen
* @date: Aug 19, 2003$
* @version: 1.0
*/
package edu.psu.geovista.app.dataloader.v1.components;


import edu.psu.geovista.app.dataloader.DfMetaData;
import edu.psu.geovista.app.dataloader.common.DataUtils;
import edu.psu.geovista.common.awt.event.dataloading.AppDataEvent;
import edu.psu.geovista.common.utils.collection.CollectionUtils;
import edu.psu.geovista.data.geog.DataSetForApps;
import edu.psu.geovista.data.model.AppDataModel;
import edu.psu.geovista.data.model.DefaultPlotModel;
import edu.psu.geovista.data.shapefile.JShapeFileDataReader;
import edu.psu.geovista.data.shapefile.ShapeFileProjection;
import edu.psu.geovista.data.shapefile.ShapeFileToShape;
import edu.psu.geovista.db.dbase.DBaseFile;
import edu.psu.geovista.io.csv.CSVFileDataReader;
import edu.psu.geovista.io.csv.GeogCSVReader;
import edu.psu.geovista.ui.event.DataSetEvent;
import edu.psu.geovista.ui.event.DataSetListener;
import epoviz.trex.common.io.csv.CSVAppDataReader;
import epoviz.trex.common.io.csv.CSVFactory;
import epoviz.trex.common.io.csv.CsvReadException;
import epoviz.trex.common.io.csv.DfCSVAppDataReader;

import javax.swing.event.EventListenerList;
import java.awt.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Vector;

public class DefaultNCIDataSource implements NCIDataSource,/*DataSetListener,*/PropertyChangeListener
{
    public static final boolean DEBUG=false;
    public static final String Prop_RowCount="prc";
    //rawDataModel and rawData store all data in the file. Act as a data cache.
    //To free memory, they should gone after use select data for analysis

    //protected MetaData metaData=new MetaData();
    protected DfMetaData metaData=new DfMetaData();
    private AppDataModel rawDataModel;
    private Object[] rawData;
    private Shape[] shapeData;

    private File dataRoot;
    private Hashtable tsValues;//time series value. A pair of attribute Name:time value

    private int projectedData=-1;//GUI setting override Meta data. But with boolean, we unable to know if this value has been set or not. Thus set it int type

    protected  int expectedNumOfRecord=-1;
    protected EventListenerList listenerList = new EventListenerList();
    PropertyChangeSupport changeSupport=new PropertyChangeSupport(this);

    public DefaultNCIDataSource() {

        //loadData();
    }
    /*******************************************************************************************************
     *                service methods
     *******************************************************************************************************/
    public void cleanData() {
        //todo
        this.rawDataModel =null;
        this.rawData =null;
        this.dataRoot =null;
        //this.setExpectedNumOfRecord(-1); //Must. Otherwise, unable to load another set of data since the value of previous session still affect.
        //Do not need it. the other source will fire propertyEvent to reset the value

        this.firePropertyEvent(Prop_RowCount,new Integer(100),new Integer(-1)); //oldvalue does not matter , but not =newValue. Otherwise the event won't be fired
        //  this.tsValue =null;
    }
    /**
     *
     * @param datafile
     * @param metaFile
     * @param shpFile      if shpFile is null, it is to be treated as non-mandatory ( useful for standalone pcp and time series)
     *                      otherwise, it means the shp file is mandatory
     * @throws Exception
     */
    public void loadFiles(String datafile,String metaFile,String shpFile) throws Exception {

        //1. Meta data
        // laterE is a exception that thrown later. Usually the purpose to throw the exception is to warning (meta file not work), but loading process still move ahead
        Exception laterE=null;
        try {
            if(metaFile!=null)
                metaData= DataUtils.loadMetaData(metaFile);//  
        }
        catch(WarningException we){
            //if unable to parse metaData, just show the msg and go aheard
            laterE=we;
        }

        /**
         *  
         */
        try {
            metaData.process();// 
        } catch (Exception e) { //UnsupportedOperationException
            String msg = e.getMessage();
            msg="There is a problem with the Meta Data file: "+metaFile+" "+"\n\n" +"        "+msg+". \n\n You may continue, but some functions will be unavailable. ";
            laterE= new WarningException(msg);
        }

        //System.out.println("obName:"+metaData.getMetaData(MetaData.MetaKey_ObName) );

        //2. Data
        rawDataModel=this.loadDataFile(datafile);
        this.rawData=rawDataModel.getGeoVistaRawData() ;
        rawDataModel.setMeta(metaData);
       // setMetaData(rawDataModel,metaData); //put the meta info into datamodel


        //2.2 Check if Ts data match Pcp data
        int numRec=this.getExpectedNumOfRecord() ;
        if(numRec>=0){//the other loader has load data
            int obRowCount = rawDataModel.getRowCount();
            if(numRec!=obRowCount ){
                this.cleanData() ;
                throw new Exception("Time Series data(rowCount="+numRec+") does not match observation data(rowCount="+obRowCount+"), unable to process");
            }

        }


        //3. shape
        if(shpFile!=null)  { //PCP data, for Time Series data won't consider shape data
            File shpf=new File(shpFile);
            if(!shpf.exists()){
               throw new FileNotFoundException("Unable to find shape file "+shpFile);
            }
            shapeData=this.loadShapeDataFile(shpFile);

            //4. simple check to make sure shapeData match observation data. For now, only check if their row counts are same
            int shpRowCount=0;
            int obRowCount=0;
            if(shapeData!=null){
                shpRowCount=shapeData.length ;
            }
            if(this.rawDataModel !=null){
                obRowCount=rawDataModel.getRowCount() ;
            }
            if(shpRowCount!=obRowCount){

                throw new Exception("shape data(rowCount="+shpRowCount+") does not match observation data(rowCount="+obRowCount+"), unable to process");
            }
        }
        else{
            // 
        }


        //5. Inform other DataSource the number of record this data source load
        //this.fireDataSetChanged(rawDataModel);//not good, since also need fire when clean data
        // do it after check loading shape data. Loading shape and loading ob is atomic process. If shape data fail to be loaded. rawDataModel is invalid
        int rowCount=this.rawDataModel.getRowCount() ;
        this.firePropertyEvent(Prop_RowCount,new Integer(-1),new Integer(rowCount));

        if(laterE!=null){
            throw laterE; //last exception
        }

        /*catch(FileNotFoundException e){
        //TODO no meta file found
        if(DEBUG)
        e.printStackTrace() ;
        }
        catch (Exception e) {
        //TODO unable to parse meta file
        if(DEBUG)
        e.printStackTrace();  //To change body of catch statement use Options | File Templates.
        }*/
    }
    /**
     *
     * @param model
     * @param meta
     */
    private void setMetaData(edu.psu.geovista.data.model.AppDataModel model, DfMetaData meta){
        // 


       /* Hashtable desc = meta.getColumndescription();
        ModelMetaData modelmeta = model.getMeta();
        modelmeta.setColumndescription(desc);*/

         //Here we only set column description, time value will be set by GUI value
    }

    public AppDataModel getRawDataModel() {
        //Must use clone, thus any operatoin on DataAssembler won't affect DataSource
        if(rawDataModel!=null) {
            Object o = ((DefaultPlotModel) rawDataModel).clone();
            AppDataModel dm=(AppDataModel) o;
            return dm ;
        }
        else
            return null;

        /* A simple way to clone, but need to take care observation data since new created model don't have observation data
        DefaultPlotModel model=new DefaultPlotModel(this.rawData );
        return model;//*/
    }
    /*public Object[] getObservationNames(){


        String obName=(String) this.metaData.getMetaData(MetaData.MetaKey_ObName);
        if(obName!=null) {
            Object[] obNames=getColumnFromRawData(rawData,obName);
            return obNames;
        }
        else{
            return new Object[0];
        }

    }*/
    public Map getAttributeTsValue() {

        //Hashtable tsValue=this.metaData.getTsValues() ;
        Map tsValue=this.metaData.getColumnValues();// .getTsValues() ;
        if(tsValue==null)return null;
        return (Map)((Hashtable) tsValue).clone() ;

    }
    public Shape[] getShapeData() {
        return shapeData;//this.rawDataModel.getShpData() ;
    }
    /**
     *
     * @param attrNames
     * @return   a array of data's name
     */
    public String[] getAvailableDataList(String[] attrNames) {
        List names=rawDataModel.getColumnNameList();
        names.remove(DfMetaData.Data_FIPS_ColName);    //"FIPS"
        String[] snames = CollectionUtils.convert2StringArrayNoNull(names);
        return snames;
        //return rawDataModel.convertListToString(names );

    }
    /*public List getDataDescriptions(){
        rawDataModel.getMeta().getColumndescription();
    }*/

    /**
     *
     * @param name    attribute name
     * @return        attribute data whichh is usually an array
     */
    public List getAttribute(String name) {
        String[] colNames=  rawDataModel.getColumnNames() ;
        List list=rawDataModel.getColumnNameList() ;

        int index=this.rawDataModel.getColumnIndexByName(name);
        //dp{
        if (DEBUG){
            System.out.println("tried to find data for :"+name);
            if(index<0){
                System.out.println("its index is:"+index);
            }
        }//dp}
        float[] values=rawDataModel.getColumnValueAsFloat(index);
        List colData=DefaultPlotModel.convertFloatToVector(values);
        return colData;
        /*int colIndex=this.getColumnIndex(name);
        if(colIndex<0)return null;//not find the column
        Object o=rawData[colIndex];
        Vector v=new Vector();
        if(o instanceof double[]){
        double[] col= ((double[])o);
        for (int i=0;i<col.length ;i++){
        v.add(new Float(col[i]));
        }
        }
        else if (o instanceof int[]){
        int[] col= ((int[])o);
        for (int i=0;i<col.length ;i++){
        v.add(new Float(col[i]));
        }

        }
        return v;*/
    }
    /*******************************************************************************************************
     *                meta data
     *******************************************************************************************************/
    public static Object[] loadCSVMetaFile(String fn) throws FileNotFoundException, CsvReadException {
        if(fn.endsWith(".csv") ){//only accept .csv file
            //CSVMetaFileReader csvR=new CSVMetaFileReader();
            CSVAppDataReader csvR= CSVFactory.getCSVAppDataReader() ;
            //csvR.read(fn);
            //Object[] raw=csvR.getRawData();
            Object[] raw = csvR.readFile(fn);
            return raw;
        }
        else{
            throw new IllegalArgumentException(fn+" is not a .csv file. Unable to process. ");
        }
    }

    /**
     * Search  key in the string <s>. Ignore case of both
     * @param s
     * @param key
     * @return
     */
    private static int indexOfStringIgnoreCase(String s,String key){
        String mys=s.toLowerCase() ;
        String mykey=key.toLowerCase() ;
        return mys.indexOf(mykey);
    }
    /**
     *
     * @param raw       
     * @param colName
     * @return
     */
    private static Object[] getColumnFromRawData(Object[] raw,String colName){
        int index = getColumnIndex(raw, colName);
        if(index>=0)
            return  (Object[]) raw[index];
        else
            return new Object[0];
    }
    /**
     * return the index of the column
     * @param raw       
     * @param colName
     * @return
     */
    private static int getColumnIndex(Object[] raw, String colName) {
        int index=-1;
        String[] header=(String[]) raw[0];
        for (int i=0;i<header.length ;i++){
            if(header[i].equalsIgnoreCase(colName) ){
                index=i+1; //1st column is header
            }
        }
        return index;
    }



    /*******************************************************************************************************
     *                load data, shape files
     *******************************************************************************************************/
    /*public void  loadObservationTsDataFile(String fn) {
    // rawDataModel
    }*/

    /**
     * Must invoke loadMetaDataFile() before invoke this method
     * @param fn  file name(including full path). Observation or Time Series data file
     */
    /*private  DefaultPlotModel  loadDataFile(String fn) {


    DefaultPlotModel rawDataModel=loadCSVDataFile(fn);
    Object[] obNames=this.getObservationNames() ;
    rawDataModel.setRowNames(DefaultPlotModel.convertToVector(obNames) );
    return rawDataModel;
    //for time series
    if(this.tsValues !=null){
    Enumeration e=tsValues.keys() ;
    while (e.hasMoreElements()) {
    String  key = (String) e.nextElement();
    Integer TV=(Integer) tsValues.get(key);
    int tv=TV.intValue() ;
    rawDataModel.addColumnValues(key,tv);
    }
    }


    }*/

    /**
     *
     * @param fn  file name(including full path) . Must be a .csv file
     * @return      Only contain numeric data, no shape data, not observation name, no time series value
     */
    private   AppDataModel loadDataFile(String fname) throws IOException {
        if(fname==null){
            new NullPointerException(this.getClass().getName() +" is passed a null argument. Unable to process.");
        }
        String fn=fname.trim().toLowerCase();
        File shp=new File(fname);


        if(!shp.exists() ){
            throw new FileNotFoundException("Given file"+fname +" does not exist. ");
        }


        //todo: check fn type .csv
        //todo: load .dbf file
        DataSetForApps dataSet=null;
        AppDataModel model=null;
        if (fn.endsWith(".csv") ) {
            dataSet = loadCSVDataFileAsDataSetForApps(fn);
            if(dataSet!=null){
                model=edu.psu.geovista.data.model.DefaultPlotModel.createDefaultPlotModel(dataSet);
                DfMetaData meta=new DfMetaData();
                model.setMeta(meta);
            }
        }
        else if(fn.endsWith(".dbf") ) {
            DBaseFile db = new DBaseFile(fn);
            Object[] dbData = db.getDataSet();
            model=DefaultPlotModel.createDefaultPlotModel(dbData);

        }
        else{
            throw new IllegalArgumentException(fname+" is neither a .csv nor a .dbf file. Unable to process");


        }



        String[] obnameColumns=(String[]) metaData.getMetaData(DfMetaData.MetaKey_ObName);
        model.setRowNameColumns(obnameColumns);
        //String[] rownames = CollectionUtils.convertToStringArray();
        Vector rowNames = model.getRowNames();
        model.setRowNames(rowNames); // 

        /*String obName=(String) metaData.getMetaData(MetaData.MetaKey_ObName);
        if(obName!=null){
            //model.set
            Object[] obNames=model.getNonNumericColumnData(obName);

            if(obNames!=null&&obNames.length >0){
                String[] obns=null;
                if (obNames instanceof String[]) {
                    obns=(String[]) obNames;
                } else {
                    obns = new String[obNames.length ];
                    for (int i=0;i<obns.length ;i++){
                        obns[i]=(String) obNames[i];
                    }
                }

                Vector rowNames = edu.psu.geovista.data.model.DefaultPlotModel.convertToVector(obns);
                model.setRowNameColumns(new String[]{obName});
                //model.setRowNameColumns(new String[]{"NAME","STATE_NAME"});
                model.setRowNames(rowNames );
            }
        }*/


        //Object[] oo=model.getGeoVistaRawData() ;
        return model;

    }

    /**
     *
     * @param fn   file name(including full path).
     * @return      shape data
     */
    private  Shape[] loadShapeDataFile (String fn) throws FileNotFoundException {
        JShapeFileDataReader shpR=new JShapeFileDataReader();
        Shape[] shp=(Shape[]) shpR.readShapeFile(fn);
        if(this.isProjectedData() )  {
            return shp;
        }
        else{//do projection
            ShapeFileProjection project=new ShapeFileProjection();
            return project.doProjection(shp);
        }
        /*ShapeFileToShape fileToShape=new ShapeFileToShape();
        if(this.isProjectedData() ){
        fileToShape.setInputDataSet(rawData);
        }
        else{
        ShapeFileProjection project=new ShapeFileProjection();
        project.setInputDataSet(rawData);
        fileToShape.setInputDataSet(project.getOutputDataSet() );
        }*/
        //return shp;
    }
    /*private void checkProject(){
        Boolean projected=(Boolean) this.metaData.getMetaData(edu.psu.geovista.app.pcp.dataloader.components.MetaData.MetaFile_Projected );
        if(projected!=null){
            boolean p=projected.booleanValue() ;
            this.setProjectedData(p);
        }

    }*/
    /*******************************************************************************************************
     *                support
     *******************************************************************************************************/

    /*******************************************************************************************************
     *                parse meta data
     *******************************************************************************************************/
    public static DataSetForApps loadCSVDataFileAsDataSetForApps(String fileName)
            throws IllegalArgumentException {
        String fn=fileName.trim().toLowerCase() ;
        if(fn.endsWith(".csv") ){//only accept .csv file
            //CSVFileDataReader csvR=new CSVFileDataReader();// 
           // CSVAppDataReader csvR= IOFactory.getCSVAppDataReader(); //new DfCSVAppDataReader();
            CSVAppDataReader csvR= new DfCSVAppDataReader();
            //GeogCSVReader csvR=new GeogCSVReader();
            DataSetForApps dataSet=null;
            try {
                Object[] rawdata = csvR.readFile(fn);
                dataSet=new DataSetForApps(rawdata);
                //csvR.setFileName(fn);
            }
            catch (NumberFormatException e) {
                if(DEBUG){
                    e.printStackTrace();
                    //System.out.println("test");
                }
                throw new IllegalArgumentException("Please specify data type at the 1st row for each column.\n"+DefaultNCIDataSource.class.getName() +".loadCSVDataFile() encounter NumberFormatException, fail to read the file.");


            }
            catch(RuntimeException re){
                //TODO If user make typo in .csv file. e.g.: in 1st row, use "sting" type instead of "String" type. Need some function to inform users the type // 
                throw new IllegalArgumentException(DefaultNCIDataSource.class.getName() +".loadCSVDataFile() encounter RuntimeException, fail to read the file.");
            }

            //DataSetForApps dataSet=csvR.getDataForApps() ;
            //csvR.
            return dataSet;
            //Get meta data information

        }
        else{
            throw new IllegalArgumentException(DefaultNCIDataSource.class.getName() +".loadCSVDataFile() is passed a non-csv file.");
        }
    }

    /* /**
    *
    * @param file  Must be a .csv file

    public void loadMetaDataFile(File file) {
    }
    /**
    *
    * @param file  Observation or Time Series data file

    public void loadDataFile(File file) {
    }
    /**
    *
    * @param file

    public void loadShapeDataFile (File file) {
    }*/

    public  void loadData(File file) throws IOException {
        this.dataRoot =file.getParentFile() ;
        String fileName=file.getCanonicalPath() ;
        //String fileName="G:/work/javaBeans/pcp/data/westcoast/westcoast.shp";//"G:/work/javaBeans/pcp/data/NCI/census80_90_00.shp";//"G:/work/javaBeans/pcp/data/westcoast/westcoast.shp";//"c:/pcphome/data/westcoast/westcoast.shp";
        //String fileName="G:/work/javaBeans/pcp/data/NCI/census80_90_00.shp";
        System.out.println("load file:"+fileName);
        if(fileName.endsWith(".csv") ){
            // 
            CSVFileDataReader csvR=new CSVFileDataReader();
            csvR.setFileName(fileName);
            //Ojbect[] rawData=csvR.getDataSet();

            DataSetForApps dataSet=csvR.getDataForApps() ;
            rawData=dataSet.getDataObjectOriginal();
            rawDataModel=DefaultPlotModel.createDefaultPlotModel();
            DfMetaData meta=new DfMetaData();
            rawDataModel.setMeta(meta);
            Object[] rawData=csvR.getDataSet() ;
            rawDataModel.feedData(rawData);

          
        }
        else{
            try {
                JShapeFileDataReader sfdr=new  JShapeFileDataReader();
                //"E:/jchen/MapBeanApp/data/westcoast.shp"
                System.out.println("ShapeFileDataReader.setFileName:-> "+fileName);
                sfdr.setFile(file);
                //sfdr.setFileNameCSV(fileName);//for using ShapeFileDataReader()
                //sfdr.setFileName(fileName);
                System.out.println("ShapeFileDataReader.setFileName:<- ");
                //sfdr.setFileName("G:/work/samples/Parvis/data/jinSinglepupil.stf");//G:\work\samples\Parvis\data
                Object[] rawData=sfdr.getDataSet() ;



                ShapeFileToShape fileToShape=new ShapeFileToShape();
                if(this.isProjectedData() ){
                    fileToShape.setInputDataSet(rawData);
                }
                else{
                    ShapeFileProjection project=new ShapeFileProjection();
                    project.setInputDataSet(rawData);
                    fileToShape.setInputDataSet(project.getOutputDataSet() );
                }

                if (rawData!=null){
                    rawDataModel=DefaultPlotModel.createDefaultPlotModel(fileToShape.getOutputDataSet() );
                    DfMetaData meta=new DfMetaData();
                    rawDataModel.setMeta(meta);
                    //rawDataModel=new  DefaultPlotModel(rawData);
                    this.rawData =rawData;
                }
            } catch (Exception e) {
                e.printStackTrace();  //To change body of catch statement use Options | File Templates.
            }
        }
    }





    private int getColumnIndex(String name){
        Object[] header=(Object[]) rawData[0];
        int colIndex=-1;
        for (int i=0;i<header.length ;i++){
            if (name.equals(header[i]) ){
                colIndex=i+1;
            }
        }
        return colIndex;
    }





    /* public String[] getTimeSeriesDataNameList(String dataName) {
    return new String[0];
    }*/


    public boolean isProjectedData() {
        if(this.projectedData ==1){
            return true;
        }
        else if(this.projectedData ==0){
            return false;
        }
        else{  //-1, not be set yet
            boolean defaultv=true;
            if(metaData==null)return defaultv;
            //Boolean p=(Boolean) metaData.getMetaData(MetaData.MetaKey_Projected) ;
            Boolean p=(Boolean) metaData.getMetaData(DfMetaData.MetaKey_Projected) ;
            if(p!=null)
                return p.booleanValue() ;
            else
                return defaultv;
        }
    }

    public void setProjectedData(boolean projectedData) {
        if(projectedData){
            this.projectedData =1;
        }
        else{
            this.projectedData=0;
        }
        //metaData.setMetaData(MetaData.MetaKey_Projected ,new Boolean(projectedData)) ;

        //this.projectedData = projectedData;
    }




    /*******************************************************************************************************
     * DataEvent
     * Data loaded by PcpDataLoader should have same number of record as that loaded bt TsDataLoader
     * Thus, the dataSource of each DataLoader must listen to each other to keep track the numOfRecord of the other
     * if the numberOfRecords are not equal, must info user
     *
     *  The reason to implement here instead of DataAssembler or DataLoader:
     *  If users load pcp and ts data before click any apply button(which fire dataSetEvent), the 2 data can be
     *  loaded even they don't match.  The key point here: the check for match must be done upon clicking load button
     *******************************************************************************************************/
    /* public void dataSetChanged(DataSetEvent e) {
    AppDataEvent appe=(AppDataEvent) e;
    AppDataModel dataModel=appe.getDataModel() ;
    int numOfRec=dataModel.getNumRecords() ;
    this.setExpectedNumOfRecord(numOfRec);
    }

    public void tsDataChange(TSDataEvent e) {
    AppDataModel dataModel=e.getDataModel() ;
    int numOfRec=dataModel.getNumRecords() ;
    this.setExpectedNumOfRecord(numOfRec);
    }*/

    public void setExpectedNumOfRecord(int expectedNumOfRecord) {
        this.expectedNumOfRecord = expectedNumOfRecord;
    }

    public int getExpectedNumOfRecord() {
        return expectedNumOfRecord;
    }
    /**
     * implements DataSetListener
     */
    public void addDataSetListener(DataSetListener l) {
        listenerList.add(DataSetListener.class, l);
    }

    /**
     * removes an DataSetListener from the button
     */
    public void removeDataSetListener(DataSetListener l) {
        listenerList.remove(DataSetListener.class, l);
    }
    protected void fireDataSetChanged(edu.psu.geovista.data.model.AppDataModel dataModel) throws Exception {
        Object[] listeners=this.listenerList.getListenerList() ;
        DataSetEvent e=null;
        for (int i=listeners.length-2 ;i>=0;i-=2){
            if(listeners[i] ==DataSetListener.class ){
                Object[] dataSet=dataModel.getGeoVistaRawData() ;
                e=new AppDataEvent(this,dataSet,dataModel);
                ((DataSetListener)listeners[i + 1]).dataSetChanged(e);
            }


        }
    }


    /*******************************************************************************************************
     *                ExpectedRowCountEvent
     * To make sure rowCount of ts loader = rowCount of pcp loader, must make sure the 2 loader know the rowCount of data loaded by the other
     * Fire the event in 2 senario:
     * - once load data
     * - once clean data (so that previous session won't affect the later one)
     *
     *******************************************************************************************************/
    public void addPropertyChangeListener(PropertyChangeListener listener)  {
        this.changeSupport.addPropertyChangeListener(listener);
    }
    public void removePropertyChangeListener(PropertyChangeListener listener)  {
        this.changeSupport.removePropertyChangeListener(listener);
    }
    private void firePropertyEvent(String propName,Object oldValue,Object newValue){
        this.changeSupport.firePropertyChange(propName,oldValue,newValue);
    }

    public void propertyChange(PropertyChangeEvent evt) {
        String pn=evt.getPropertyName() ;
        if(pn.equals(Prop_RowCount) ){
            Integer rowCount=(Integer) evt.getNewValue() ;
            this.setExpectedNumOfRecord(rowCount.intValue() );
        }
    }
   




}
